package column


import (
	"github.com/treelab/updatecolumn/models"
)

  func instanceType() int32 {
    return models.Formula
  }

  deleteColumnEdges(command: UpdateColumn_v2, o: CommonOptions) {
    const { column } = command;
    const dependentColumns = column.hasDependentColumn.get();
    const dependentColumnIds = dependentColumns.map((dependentColumn: any) => dependentColumn.uid);
    o.delQuads.push(
      ...this.dgraphHelperService.getRemoveEdgeQuads(dependentColumnIds, [Relation.FROM_FORMULA_COLUMN], column.uid!),
    );
    o.delQuads.push(
      ...this.dgraphHelperService.getRemoveEdgeQuads(column.uid!, [
        'formula',
        'errorCode',
        'resultType',
        'numberFormat',
        'precision',
        'currencyFormat',
        'dateFormat',
        'timeFormat',
        'includeTime',
        Relation.HAS_DEPENDENT_COLUMN,
      ]),
    );
    return this;
  }

  public hasNothingToUpdate(command: UpdateColumn_v2) {
    const { column } = command;
    const formulaColumnConfig = command.columnConfig as FormulaColumnConfig;
    let oldFormula = column.formula || '';
    column.hasDependentColumn.get().forEach((c: any, order: number) => {
      oldFormula = oldFormula.replace(new RegExp(`column_value_${order}`, 'g'), `column_value_${c.uid}`);
    });
    return (
      column.columnType === this.instanceType &&
      !column.errorCode &&
      column.currencyFormat === formulaColumnConfig.currencyFormat &&
      column.dateFormat === formulaColumnConfig.dateFormat &&
      oldFormula === formulaColumnConfig.formula &&
      column.includeTime === formulaColumnConfig.includeTime &&
      column.numberFormat === formulaColumnConfig.numberFormat &&
      column.precision === formulaColumnConfig.precision &&
      column.resultType === formulaColumnConfig.resultType &&
      column.timeFormat === formulaColumnConfig.timeFormat
    );
  }

  async updateColumn(
    command: UpdateColumn_v2,
    o: CommonOptions,
    helper: { rows?: Row[]; columnCopy?: Column; referenceColumns?: Column[]; userGroup?: any },
  ) {
    await super.updateColumn(command, o, helper);

    const { column, columnConfig } = command;
    const { formula, errorCode, currencyFormat } = columnConfig as FormulaColumnConfig;
    let { resultType } = columnConfig as FormulaColumnConfig;
    const dependencies = Object.keys(FormulaExecutor.getDependencies(formula));
    const dependenciesColumns = await this.getColumnType(dependencies, ['columnType', 'resultType']);
    let _formula = formula;
    // XXX: Does this escape break any of the formulas?
    column.resultType = resultType!;
    if (errorCode) {
      column.errorCode = errorCode;
    } else {
      _formula = this.updateDependentColumn(column, _formula, dependencies, o);
      column.formula = formula;

      const { numberFormat, precision, dateFormat, timeFormat, includeTime } = columnConfig as FormulaColumnConfig;
      if (!resultType) {
        (columnConfig as FormulaColumnConfig).resultType = resultType = this.getResultType(column, dependenciesColumns);
      }
      if (resultType === FormulaResultType.NUMBER) {
        if (numberFormat === NumberFormat.DECIMAL || numberFormat === NumberFormat.PERCENTAGE) {
          column.numberFormat = numberFormat;
          column.precision = precision!;
        } else if (numberFormat === NumberFormat.CURRENCY) {
          column.numberFormat = numberFormat;
          column.precision = precision!;
          column.currencyFormat = currencyFormat!;
        }
      } else if (resultType === FormulaResultType.DATE) {
        if (dateFormat) {
          column.dateFormat = dateFormat;
          column.includeTime = includeTime!;
          column.timeFormat = timeFormat!;
        }
      }
      column.formula = _formula;
    }
    if (resultType) {
      // update resultType
      column.resultType = resultType;
    }
    return this;
  }

  updateDependentColumn(column: Column, formula: string, dependencies: string[], o: CommonOptions) {
    _.forEach(dependencies, (dependentId, i) => {
      formula = formula.replace(new RegExp(`column_value_${dependentId}`, 'g'), `column_value_${i}`);
      const dependentColumn = o!.ormTransaction.nodeFor(Column, { uid: dependentId });
      column.hasDependentColumn.withFacet(new OrderFacet({ order: i })).add(dependentColumn);
      dependentColumn.fromFormulaColumn.add(column);
    });
    return formula;
  }

  async convertColumn(
    command: UpdateColumn_v2,
    o: CommonOptions,
    helper: { rows?: Row[]; columnCopy?: Column; referenceColumns?: Column[]; userGroup?: any },
  ) {
    const { column } = command;
    const formulaConfig = command.columnConfig as FormulaColumnConfig;

    let oldFormula = column.formula || '';
    column.hasDependentColumn.get().forEach((c, order: number) => {
      oldFormula = oldFormula.replace(new RegExp(`column_value_${order}`, 'g'), `column_value_${c.uid}`);
    });
    // No conversion needed if formula never changed.
    if (
      formulaConfig.formula === oldFormula &&
      !column.errorCode &&
      formulaConfig.numberFormat === column.numberFormat &&
      formulaConfig.precision === column.precision &&
      formulaConfig.currencyFormat === column.currencyFormat &&
      formulaConfig.dateFormat === column.dateFormat &&
      formulaConfig.timeFormat === column.timeFormat &&
      formulaConfig.includeTime === column.includeTime &&
      formulaConfig.useGMT === column.useGMT
    ) {
      return this;
    }

    const dependentIds = Object.keys(FormulaExecutor.getDependencies(formulaConfig.formula));
    const dependentColumnTransaction = await this.columnReadService.getColumnAllColumnObjByColumnIds(dependentIds, {
      includeCell: true,
      removeBaseEdges: true,
    });
    const dependentColumns = dependentColumnTransaction.tree;

    this.checkForFormulaErrors(dependentColumns);

    const dependencies = column.hasDependentColumn.get().map(c => c.uid!);

    if (dependencies.includes(column.uid!)) {
      throw new BadRequestException('Formula cannot refer to self');
    }

    if (dependentColumns.length !== dependentIds.length) {
      // @TODO Report error detail
      throw new ColumnNotExistError();
    }
    this.checkFormulaCyclic(column.uid!, dependentColumns);

    await this.defaultConvert({ command }, o, helper);

    column.hasDependentColumn.deleteAll();
    for (const dependentColumn of dependentColumns) {
      column.hasDependentColumn.add(dependentColumn);
    }
    return this;
  }

  protected checkForFormulaErrors(dependentColumns: Column[]) {
    _.forEach(dependentColumns, dependentColumn => {
      if (dependentColumn.errorCode) {
        throw new FormulaColumnInvalidError(dependentColumn.uid!);
      }
    });
  }

  protected checkFormulaCyclic(columnId: string, dependentColumns: Column[] | readonly Column[]) {
    _.forEach(dependentColumns, dependentColumn => {
      const _dependentColumns = dependentColumn.hasDependentColumn.get();
      if (!_dependentColumns.length) {
        return;
      }
      const _dependencies = _dependentColumns.map(c => c.uid!);
      if (_dependencies.includes(columnId)) {
        throw new CircularReferenceError();
      }
      this.checkFormulaCyclic(columnId, _dependentColumns);
    });
  }

  public getColumnTypeOptions(column: Column, columnConfig?: any): { name: any; type: any; typeOptions: string } {
    const result = super.getColumnTypeOptions(column, columnConfig);
    if (columnConfig) {
      return result;
    }
    let { formula } = column;
    const dependentColumns = column.hasDependentColumn.get();
    if (column.errorCode) {
      return {
        name,
        type: ColumnType.FORMULA,
        typeOptions: JSON.stringify({ errorCode: column.errorCode }),
      };
    }
    const dependencies = Object.keys(FormulaExecutor.getDependencies(formula));
    dependencies.forEach((order: string) => {
      formula = formula.replace(
        new RegExp(`column_value_${order}`, 'g'),
        `column_value_${dependentColumns[+order].uid}`,
      );
    });
    const typeOptions = JSON.parse(result.typeOptions);
    typeOptions.resultType = column.resultType || FormulaResultType.TEXT;
    typeOptions.formula = formula;
    return Object.assign(result, { typeOptions: JSON.stringify(typeOptions) });
  }

